package com.fractal;

import java.util.ArrayList;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.Log;
import android.view.SurfaceHolder;

public class MandelGenerator implements Runnable
{
	//Thread stuff
    private Thread thread;
	private boolean running;
	
	//"Work area" stuff
	private short[][] buffer;
    private Stack stack;
    private SurfaceHolder surface;
    private Bitmap raster;
    
    //Collaborates
    private Palette palette;
    private GetDatabase bookmarks;
    
    // Image state
    private int imgWidth, imgHeight;
    private short maxIte;
    private double pixelSize, x0, y0;
    
    //temp change holders
    private double dx, dy;
    private double zoomFactor;
    private short newMax;
    private int newPalette;
    private byte changeFlag;
    private GetDatabase newDb;
    
    //Constants
    private final Rect initialRect;
    private final byte resetFlag = 1;
    private final byte zoomFlag = 2;
    private final byte translateFlag = 3;
    private final byte iterationFlag = 4;
    private final byte paletteFlag = 5;
    private final byte bookmarkFlag = 6;
    
    public MandelGenerator(int w, int h, SurfaceHolder s, Bitmap img)
    { 
    	imgWidth = w;
    	imgHeight = h;
    	
    	initialRect = new Rect(0, 0, imgWidth-1, imgHeight-1);    	
    	buffer = new short[w][h];
    	stack = new Stack();
    	raster = img;
    	surface = s;
    	thread = null;    	
    }

    public void translate(double dx, double dy)
    {
    	this.dx = dx;
    	this.dy = dy;
    	changeFlag = translateFlag;
       	render();    	
    }
    
    public void zoom(double zoom)
    {
    	if (zoom > 0)
    		maxIte += 20;
    	else
    		maxIte -= 20;
    	
    	zoomFactor = zoom;
    	changeFlag = zoomFlag;
    	render();
    }
    
    public void setMaxIterations(short max)
    {
    	this.newMax = max;
    	changeFlag = iterationFlag;
       	render();
    }
    
    public void setPalette(int type)
    {
    	this.newPalette = type;
    	changeFlag = paletteFlag;
    	render();
    }
    
    public void setBookmarks(GetDatabase db)
    {
    	this.newDb = db;
    	changeFlag = bookmarkFlag;
    	render();
    }

    public void reset(GetDatabase db)
    {
    	newDb = db;
    	x0 = -2.5d;
    	pixelSize = 5d / imgWidth;
    	y0 = imgHeight * pixelSize * 0.5;
    	maxIte = 20;
    	changeFlag = resetFlag;
       	render();
    }
    
    public ArrayList<Double> getInfo(){
    	ArrayList<Double> array = new ArrayList<Double>();
    	double x0 = this.x0 + 0.5 * pixelSize * imgWidth;
    	double y0 = this.y0 - 0.5 * pixelSize * imgHeight;
    	array.add(x0);
    	array.add(y0);
    	array.add(pixelSize);
		return array;
    }
    
    public void render()
    {
    	stack.push(initialRect);
    }
    
    public void startThread()
    {
    	if (this.thread == null)
    	{
    		this.running = true;
    		this.thread = new Thread(this);
    		this.thread.start();
    	}
    	render();
    }
    
    public void stopThread()
    {
    	if(thread != null)
    	{
    		this.running = false;
    		this.thread.interrupt();
    		while(true)
    		{
    			try 
    			{
    				this.thread.join();
    				this.thread = null;
    				break;
    			}
    			catch (InterruptedException e)
    			{}
    		}
    	}
    }
    
    public void pauseThread()
    {
    	stack.clean();
    }
    
    /* the actual rendering, and the helper methods are below */
    
    public void run()
    {
    	Rect r;
    	Rect draw = new Rect();
    	Canvas c = null;

    	ArrayList<Float> cxs = new ArrayList<Float>();
    	ArrayList<Float> cys = new ArrayList<Float>(); 
    	ArrayList<Double> xs = new ArrayList<Double>();
    	ArrayList<Double> ys = new ArrayList<Double>();

    	Paint circleOuter = new Paint(Paint.ANTI_ALIAS_FLAG);
    	Paint circleInner = new Paint(Paint.ANTI_ALIAS_FLAG);
    	circleInner.setARGB(0xFF, 0x00, 0x00, 0xFF);
    	circleOuter.setARGB(0xFF, 0xFF, 0xFF, 0xFF);

    	while(running)
    	{
    		try
    		{
    			if (stack.size() == 0 && bookmarks != null) // nothing left - redraw the img to remove the artifacts in canvas
    			{
    				cxs.clear();
    				cys.clear();
    				xs.clear();
    				ys.clear();

    				bookmarks.getIntersection(xs, ys, pixelSize, x0, y0, pixelSize*imgWidth, pixelSize*imgHeight);
    				
    				for (double x : xs)
    				{
    					cxs.add((float) Math.round((x-x0)/pixelSize));
    				}

    				for (double y : ys)
    				{
    					cys.add((float) Math.round((y0-y)/pixelSize));
    				}
    				
    				while (c == null)
    					c = surface.lockCanvas(null);    				
    				
    				Log.d("generator", "add thoes points!");
    				try 
    				{
    					if (c != null)
    						synchronized (surface) 
    						{
    							c.drawBitmap(raster, 0, 0, null);
    							for (int i = 0; i < cys.size(); i++)
    							{
    								c.drawCircle(cxs.get(i), cys.get(i), 6, circleOuter);
    								c.drawCircle(cxs.get(i), cys.get(i), 4, circleInner);
    							}
    						}
    				}
    				finally
    				{
    					if ( c != null )
    					{
    						surface.unlockCanvasAndPost(c);
    						c = null;
    					} 
    				}
    			}
    			   			
    			r = stack.pop();
    			if (r == this.initialRect)
    			{
    				stack.clean();
    				switch (changeFlag)
    				{
    				case zoomFlag: 
        				for (int j = 0; j < r.height(); j++)
        					for (int i = 0; i < r.width(); i++)
        						buffer[i][j] = -1;
        				if (zoomFactor < 1)
        				{
        					x0 += (0.25d * imgWidth * pixelSize);
        					y0 -= (0.25d * imgHeight * pixelSize);
        				}
        				else if (zoomFactor > 1)
        				{
        					x0 -= (0.5d * imgWidth * pixelSize);
        					y0 += (0.5d * imgHeight * pixelSize);
        				}
        					
        				pixelSize *= zoomFactor;
        				zoomFactor = 1;
        				break;
    				case translateFlag: //  TODO can we translate the shorts in the array without getting any clipping ???
        				for (int j = 0; j < r.height(); j++)
        					for (int i = 0; i < r.width(); i++)
        						buffer[i][j] = -1;
        				x0 -= dx * pixelSize;
        				y0 += dy * pixelSize; 
        				dx = dy = 0;
        				break;
    				case resetFlag:
        				for (int j = 0; j < r.height(); j++)
        					for (int i = 0; i < r.width(); i++)
        						buffer[i][j] = -1;
        				if (newDb != null)
        					bookmarks = newDb;
        				newDb = null;
    					break;
    				case iterationFlag:
        				for (int j = 0; j < r.height(); j++)
        					for (int i = 0; i < r.width(); i++)
        						buffer[i][j] = ( buffer[i][j] == maxIte || buffer[i][j] > newMax ? -1 : buffer[i][j] );
        				maxIte = newMax;
        				newMax = 0;
    					break;
    				case paletteFlag:
    					palette = new Palette(newPalette);
    					//no need to invalidate any part of the buffer
    					break;
    				case bookmarkFlag:
    					bookmarks = newDb;
    					newDb = null;
    					//no need to invalidate any part of the buffer
    					break;
    				}
    				changeFlag = 0;
    				divideJob(r);
    				continue;
    			}
    			
    			if ( fill(r) )
    			{  // the rect was filled - add it to the canvas
    				for (int j = r.top; j <= r.bottom; j++)
    					for (int i = r.left; i <= r.right; i++)
    						raster.setPixel(i, j, iteToColor(buffer[i][j]));
    				
    				draw.top = r.top;
    				draw.left = r.left;
    				draw.right = r.right + 1;
    				draw.bottom = r.bottom + 1; // android has a different view of how width is used
    				
    				c = surface.lockCanvas(draw);
    				
    				if (c != null)
    					synchronized (surface) 
    					{
    						c.drawBitmap(raster, draw, draw, null);
    					}
    			}
    			else
    			{
    	    		divideJob(r);
    			}
    		}
    		catch (InterruptedException e) { }
	        finally 
	        {
	            if (c != null) 
	            {
	            	surface.unlockCanvasAndPost(c);
	            	c = null;
	            }
	        }    				
    	}
    }
    
    private int iteToColor(short ite)
    {
    	if (palette == null)
    		palette = new Palette(Palette.FIRE);

    	if (ite == maxIte)
    		return palette.getARGB();
    	return palette.getARGB((int)ite);
    }

    private boolean fill(Rect r)
    {
    	double x0 = this.x0 + r.left*pixelSize;
    	double y0 = this.y0 - r.top*pixelSize;
    	
    	if (r.width() * r.height() > 1000)
    	{
	    	boolean isSame = true;
	    	short targetValue = buffer[r.left][r.top];
	    	
	    	for (int i = r.left; i <= r.right; i++)
	    	{
	    		buffer[i][r.top] = (buffer[i][r.top] == -1 ? iteratePoint(x0 + (i-r.left) * pixelSize, y0) : buffer[i][r.top]);
	    		buffer[i][r.bottom] = (buffer[i][r.bottom] == -1 ? iteratePoint(x0 + (i-r.left) * pixelSize, y0 - r.height() * pixelSize) : buffer[i][r.bottom]);
	
	    		isSame &= (buffer[i][r.top] == targetValue);
	    		isSame &= (buffer[i][r.bottom] == targetValue);
	    	}
	
	    	for (int j = r.top; j <= r.bottom; j++)
	    	{
	    		buffer[r.left][j] = (buffer[r.left][j] == -1 ? iteratePoint(x0, y0 - (j-r.top) * pixelSize) : buffer[r.left][j]);
	    		buffer[r.right][j] = (buffer[r.right][j] == -1 ? iteratePoint(x0 + r.width() * pixelSize, y0 - (j-r.top) * pixelSize) : buffer[r.right][j]);
	
	    		isSame &= (buffer[r.left][j] == targetValue);
	    		isSame &= (buffer[r.right][j] == targetValue);
	    	}
	
	    	if  ( isSame || r.width()+1 <= 2 || r.height()+1 <= 2 )
	    	{
	    		for (int j = r.top+1; j < r.bottom; j++)
	    			for (int i = r.left+1; i < r.right; i++)
	    				buffer[i][j] = targetValue;
	    		return true;
	    	}
	    	else
	    	{
	    		return false;
	    	}
    	}
    	else
    	{
	    	for (int j = r.top; j <= r.bottom; j++)
		    	for (int i = r.left; i <= r.right; i++)
		    		buffer[i][j] = (buffer[i][j] == -1 ? iteratePoint(x0 + (i-r.left) * pixelSize, y0 - (j-r.top) * pixelSize) : buffer[i][j]);

    		return true;
    	}

    }

    private void divideJob(Rect r)
    {	
    	int p1_left, p1_top, p1_right, p1_bottom;
    	int p2_left, p2_top, p2_right, p2_bottom;

		p1_left = r.left;
		p1_top = r.top;
		
		p2_right = r.right;
		p2_bottom = r.bottom;
		
    	if (r.height() > r.width())
    	{
    		p1_right = r.right;
    		p1_bottom = p1_top + r.height() / 2;
    		
    		p2_left = r.left;
    		p2_top = p1_bottom + 1;
    	}
    	else // l >= h
    	{
    		p1_right = p1_left + r.width() / 2;
    		p1_bottom = r.bottom;
    		
    		p2_left = p1_right + 1;
    		p2_top = r.top;
    	}
    	// rect constructor(left, top, right, bottom)
    	Rect r1 = new Rect (p1_left, p1_top, p1_right, p1_bottom);
     	Rect r2 = new Rect (p2_left, p2_top, p2_right, p2_bottom);
     	
    	stack.push(r1);
    	stack.push(r2);
    }

    private short iteratePoint(double x0, double y0)
    {
    	double x, y, x2, y2;
    	short ite = 0;
    	x = x0;
    	y = y0;
    	x2 = x*x;
    	y2 = y*y;

    	while (ite < this.maxIte && x2 + y2 <= 4.0d)
    	{
    		y = 2 * x * y + y0;
    		x = x2 - y2 + x0;
    		x2 = x * x;
    		y2 = y * y;
    		ite++;
    	}
    	return ite;
    }
}